package cn.flying.cloud.flow.web;

import javax.annotation.Resource;
import javax.validation.constraints.NotBlank;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.Date;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

import org.apache.commons.lang3.time.DateFormatUtils;
import org.flowable.engine.RuntimeService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import cn.flying.cloud.base.common.enums.RtCodeEnum;
import cn.flying.cloud.base.common.exception.BizException;
import cn.flying.cloud.base.common.utils.Ft;
import cn.flying.cloud.base.common.utils.lang.RandomUtil;
import cn.flying.cloud.base.common.vo.PageRequest;
import cn.flying.cloud.base.common.vo.PageResponse;
import cn.flying.cloud.base.common.vo.Rt;
import cn.flying.cloud.flow.core.service.ProcessDefinitionService;
import cn.flying.cloud.flow.core.service.ProcessManageService;
import cn.flying.cloud.flow.model.FlowAuditVo;
import cn.flying.cloud.flow.model.FlowStartVo;
import cn.flying.cloud.flow.model.ProcDefinition;
import cn.flying.cloud.flow.model.ProcException;
import cn.flying.cloud.flow.model.ProcHisInstance;
import cn.flying.cloud.flow.model.ProcHisTask;
import cn.flying.cloud.flow.model.ProcInstance;
import cn.flying.cloud.flow.model.ProcTask;
import cn.flying.cloud.flow.service.FwTaskCandidateService;

/**
 * 流程管理处理服务
 *
 * @author: admin
 * @date: 2022年09月29日 20:11
 * @version: 1.0
 */
@RestController
@RequestMapping("/flow")
public class ProcessManageController {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Resource
    private ProcessDefinitionService processDefinitionService;
    @Resource
    private ProcessManageService processManageService;
    @Resource
    private FwTaskCandidateService taskCandidateService;
    @Resource
    private RuntimeService runtimeService;

//    @RequestMapping("hello")
//    public void hello() {
//        String procInst = "1879560227396616192";
//        //2.回退节点
//        runtimeService.createChangeActivityStateBuilder()
//                //指定流程实例ID
//                .processInstanceId(procInst)
//                //指定源节点和目标节点
//                .moveActivityIdTo("sid-F1A12AAE-D3A7-4178-92C9-0C5D1BED467C", "sid-FA0C0E54-EA0D-4322-BD61-0E300E43915A")
//                .changeState();
//        System.out.println("驳回完成");
//    }

    /**
     * 分页查询流程定义列表
     *
     * @param request
     * @return
     */
    @PostMapping(value = "definition/page")
    public PageResponse<ProcDefinition> getProcessDefinitionPageList(@RequestBody PageRequest<ProcDefinition> request) {
        String tenantId = Ft.Local.TenantId();
        request.getParam().setTenantId(tenantId);
        return processDefinitionService.getProcessDefinitionPageList(request);
    }

    /**
     * 新增部署流程定义
     *
     * @param file 流程定义文件
     * @return
     * @throws Exception
     */
    @PostMapping("definition/deploy")
    public Rt<?> deploy(@RequestParam("file") MultipartFile file) throws Exception {
        String tenantId = Ft.Local.TenantId();
        Optional.ofNullable(tenantId).orElseThrow(() -> new BizException(RtCodeEnum.R10009));
        String id = processDefinitionService.deploy(file, "", tenantId);
        return Rt.success("流程部署成功！", "流程定义ID【" + id + "】");
    }

    /**
     * 删除部署流程定义
     *
     * @param deploymentId 流程部署ID
     * @param cascade      是否同时删除流程实例
     * @return
     */
    @PostMapping("definition/delete")
    public Rt<?> delete(@NotBlank(message = "流程部署ID不能为空") @RequestParam String deploymentId, Boolean cascade) {
        String tenantId = Ft.Local.TenantId();
        if (Objects.isNull(cascade)) {
            cascade = true;
        }
        //true会同时删除流程实例
        processDefinitionService.delete(deploymentId, tenantId, cascade);
        return Rt.success("流程删除成功！");
    }

    /**
     * 挂起流程定义
     *
     * @param processDefId 流程定义ID
     * @return
     */
    @PostMapping("definition/suspend")
    public Rt<?> suspend(@NotBlank(message = "流程定义ID不能为空") @RequestParam String processDefId) {
        String tenantId = Ft.Local.TenantId();
        processDefinitionService.suspend(processDefId, tenantId);
        return Rt.success("流程挂起成功！");
    }

    /**
     * 激活流程定义
     *
     * @param processDefId 流程定义ID
     * @return
     */
    @PostMapping("definition/active")
    public Rt<?> active(@NotBlank(message = "流程定义ID不能为空") @RequestParam String processDefId) {
        String tenantId = Ft.Local.TenantId();
        processDefinitionService.active(processDefId, tenantId);
        return Rt.success("流程激活成功！");
    }

    /**
     * 查看流程部署定义图
     *
     * @param processDefId 流程定义ID
     * @throws Exception
     */
    @GetMapping("definition/process/diagram")
    public ResponseEntity<byte[]> getProcessDiagram(@NotBlank(message = "流程定义ID不能为空") @RequestParam String processDefId) {
        try (InputStream ins = processDefinitionService.getProcessDiagram(processDefId); ByteArrayOutputStream outputStream = new ByteArrayOutputStream();) {
            byte[] buffer = new byte[1024];
            int length;
            while ((length = ins.read(buffer)) != -1) {
                outputStream.write(buffer, 0, length);
            }
            byte[] data = outputStream.toByteArray();
            return ResponseEntity.ok()
                    .contentType(MediaType.IMAGE_PNG)
                    .body(data);
        } catch (Exception e) {
            logger.error("流程图生成失败", e);
            throw new BizException(RtCodeEnum.R_ERROR.getCode(), "流程图生成失败！");
        }
    }
//    public void getProcessDiagram(HttpServletResponse response, @NotBlank(message = "流程定义ID不能为空") @RequestParam String processDefId) {
//        try (InputStream ins = processDefinitionService.getProcessDiagram(processDefId); OutputStream out = response.getOutputStream()) {
//            response.setContentType("image/png");
//            BufferedImage image = ImageIO.read(ins);
//            ImageIO.write(image, "png", out);
//        } catch (Exception e) {
//            logger.error("流程图生成失败", e);
//            throw new BizException(RtCodeEnum.R_ERROR.getCode(), "流程图生成失败！");
//        }
//    }

    /**
     * 下载流程xml文件
     *
     * @param processDefId 流程定义ID
     * @throws Exception
     */
    @GetMapping("definition/process/xml")
    public ResponseEntity<InputStreamResource> getProcessXml(@NotBlank(message = "流程定义ID不能为空") @RequestParam String processDefId) {
        return processDefinitionService.getProcessXml(processDefId);
    }

    /**
     * 查询流程定义key
     *
     * @return
     */
    @PostMapping("definition/process/keys")
    public Rt<?> getProcDefKeys() {
        String tenantId = Ft.Local.TenantId();
        Optional.ofNullable(tenantId).orElseThrow(() -> new BizException(RtCodeEnum.R10009));
        Map<String, String> map = processDefinitionService.getProcDefKeys(tenantId);
        return Rt.success(map);
    }

    /**
     * 根据流程定义ID查询所有的人工节点
     *
     * @param processDefId 流程定义ID
     * @return
     */
    @PostMapping("definition/getAllUserTask")
    public Rt<?> getAllUserTask(@NotBlank(message = "流程定义ID不能为空") @RequestParam String processDefId) {
        Map<String, String> map = processDefinitionService.getAllUserTask(processDefId);
        return Rt.success(map);
    }

    /**
     * 新增候选组
     *
     * @param name 名称
     * @param type 类型
     * @return
     */
    @PostMapping("definition/group/add")
    public Rt<?> addGroup(String name, String type) {
        processDefinitionService.addGroup("", name, type);
        return Rt.success("添加成功！");
    }

    /**
     * 分页查询流程实例列表
     *
     * @param request
     * @return
     */
    @PostMapping("instance/page")
    public PageResponse<ProcInstance> getProcessInstPageList(@RequestBody PageRequest<ProcInstance> request) {
        return processManageService.getProcessInstancePageList(request);
    }

    /**
     * 分页查询流程实例历史列表
     *
     * @param request
     * @return
     */
    @PostMapping("instance/his/page")
    public PageResponse<ProcHisInstance> getProcessHisInstPageList(@RequestBody PageRequest<ProcHisInstance> request) {
        return processManageService.getProcessHisInstancePageList(request);
    }

    /**
     * 分页查询我发起的流程实例历史列表
     *
     * @param request
     * @return
     */
    @PostMapping("instance/start/his/page")
    public PageResponse<ProcHisInstance> getStartProcessInstPageList(@RequestBody PageRequest<ProcHisInstance> request) {
        request.getParam().setStartUserId(Ft.Local.UserId());
        return processManageService.getProcessHisInstancePageList(request);
    }

    /**
     * 启动流程
     *
     * @param processDefKey 流程定义key
     * @param bizKey        业务主键
     * @param procInstId    流程实例ID
     * @param name          流程名称
     * @param variables     当前变量（Map结构）
     * @return
     */
    @PostMapping("instance/start/{processDefKey}")
    public Rt<?> startProcess(@PathVariable("processDefKey") String processDefKey, @RequestParam String bizKey, @RequestParam String procInstId, @RequestParam String name, @RequestBody Map<String, Object> variables) {
        if (Ft.String.isBlank(bizKey)) {
            bizKey = DateFormatUtils.format(new Date(), "yyyyMMddHHmmss") + RandomUtil.getRandomNumber(4);
        }

        FlowStartVo process = new FlowStartVo();
        process.setProcDefKey(processDefKey);
        process.setBizKey(bizKey);
        process.setProcInstId(procInstId);
        process.setName(name);
        process.setTenantId(Ft.Local.TenantId());
        process.setStartUserId(Ft.String.isNotBlank(Ft.Local.UserId()) ? Ft.Local.UserId() : "10000");
        process.setVariables(variables);

        String processInstanceId = processManageService.startProcess(process);
        return Rt.success("流程启动成功！", "流程实例ID：【" + processInstanceId + "】");
    }

    /**
     * 启动流程
     *
     * @param process 流程信息
     * @return
     */
    @PostMapping("instance/start")
    public Rt<?> startProcess(@RequestBody FlowStartVo process) {
        if (Ft.String.isBlank(process.getBizKey())) {
            process.setBizKey(DateFormatUtils.format(new Date(), "yyyyMMddHHmmss") + RandomUtil.getRandomNumber(4));
        }

        process.setTenantId(Ft.Local.TenantId());
        process.setStartUserId(Ft.String.isNotBlank(Ft.Local.UserId()) ? Ft.Local.UserId() : "10000");

        String processInstanceId = processManageService.startProcess(process);
        return Rt.success("流程启动成功！", "流程实例ID：【" + processInstanceId + "】");
    }

    /**
     * 流程撤回
     *
     * @param procInstId 流程实例ID
     * @return
     */
    @PostMapping("instance/cancel")
    public Rt<?> cancelProcess(@NotBlank(message = "流程实例ID不能为空") @RequestParam String procInstId) {
        processManageService.cancelProcess(procInstId);
        return Rt.success("流程撤回成功！");
    }

    /**
     * 流程作废（删除实例）
     *
     * @param procInstId 流程实例ID
     * @param reason     原因备注
     * @return
     */
    @PostMapping("instance/delete")
    public Rt<?> deleteProcess(@NotBlank(message = "流程实例ID不能为空") @RequestParam String procInstId, String reason) {
        processManageService.deleteProcess(procInstId, reason);
        return Rt.success("流程删除成功！");
    }


    /**
     * 查询流程实例任务
     *
     * @param request
     * @return
     */
    @PostMapping("instance/task/list")
    public PageResponse<ProcTask> processTask(@RequestBody PageRequest<ProcTask> request) {
        String tenantId = Ft.Local.TenantId();
        request.getParam().setTenantId(tenantId);
        return processManageService.processTask(request);
    }

    /**
     * 查询未分配代理人的任务（未领取）
     *
     * @param request
     * @return
     */
    @PostMapping("instance/task/unassigned")
    public PageResponse<ProcTask> unassignedTasks(@RequestBody PageRequest<ProcTask> request) {
        String tenantId = Ft.Local.TenantId();
        request.getParam().setTenantId(tenantId);
        return processManageService.unassignedTasks(request);
    }

    /**
     * 领取任务
     *
     * @param taskId   任务ID
     * @param assignee 代理人
     */
    @PostMapping("instance/task/claim")
    public Rt<?> claim(@NotBlank(message = "任务ID不能为空") @RequestParam String taskId, @NotBlank(message = "代理人不能为空") @RequestParam String assignee) {
        processManageService.claimTask(taskId, assignee);
        logger.info("用户【{}】成功领取任务【{}】！", assignee, taskId);
        return Rt.success("任务领取成功！");
    }

    /**
     * 归还领取的任务
     *
     * @param taskId   任务ID
     * @param assignee 代理人
     */
    @PostMapping("instance/task/unclaim")
    public Rt<?> unclaim(@NotBlank(message = "任务ID不能为空") @RequestParam String taskId, @NotBlank(message = "代理人不能为空") @RequestParam String assignee) {
        processManageService.unclaimTask(taskId, assignee);
        logger.info("用户【{}】成功归还任务【{}】！", assignee, taskId);
        return Rt.success("任务归还成功！");
    }

    /**
     * 委派任务
     *
     * @param taskId   任务ID
     * @param assignee 代理人
     */
    @PostMapping("instance/task/delegate")
    public Rt<?> delegateTask(@NotBlank(message = "任务ID不能为空") @RequestParam String taskId, @NotBlank(message = "代理人不能为空") @RequestParam String assignee) {
        processManageService.delegateTask(taskId, assignee);
        logger.info("成功委派任务【{}】给用户【{}】！", assignee, taskId);
        return Rt.success("任务委派成功！");
    }

    /**
     * 指派任务 （指派、转办、转签）任务（分配任务处理人） 转签就是将任务的负责人直接设置为别人。即本来由自己办理，改为别人办理。
     *
     * @param taskId   任务ID
     * @param assignee 代理人
     */
    @PostMapping("instance/task/assign")
    public Rt<?> assignTask(@NotBlank(message = "任务ID不能为空") @RequestParam String taskId, @NotBlank(message = "代理人不能为空") @RequestParam String assignee) {
        processManageService.assignTask(taskId, assignee);
        logger.info("成功指派任务【{}】给用户【{}】！", taskId, assignee);
        return Rt.success("任务指派成功！");
    }

    /**
     * 查询我的待办任务
     *
     * @param request
     * @return
     */
    @PostMapping("instance/task/incomplete")
    public PageResponse<ProcTask> incompleteTasks(@RequestBody PageRequest<ProcTask> request) {
        request.getParam().setTenantId(Ft.Local.TenantId());
        if (Ft.String.isBlank(request.getParam().getAssignee())) {
            request.getParam().setAssignee(Ft.Local.UserId());
        }
        return processManageService.incompleteTasks(request);
    }

    /**
     * 完成提交任务
     *
     * @param auditVo
     * @return
     */
    @PostMapping("instance/task/complete")
    public Rt<?> completeTask(@RequestBody FlowAuditVo auditVo) {
        processManageService.completeTask(auditVo);
        return Rt.success("完成提交任务成功！");
    }

    /**
     * 查询我的已办任务
     *
     * @param request
     * @return
     */
    @PostMapping("instance/task/completed/his")
    public PageResponse<ProcHisTask> completedHisTasks(@RequestBody PageRequest<ProcHisTask> request) {
        String tenantId = Ft.Local.TenantId();
        request.getParam().setTenantId(tenantId);
        if (Ft.String.isBlank(request.getParam().getAssignee())) {
            request.getParam().setAssignee(Ft.Local.UserId());
        }
        return processManageService.completedHisTasks(request);
    }

    /**
     * 触发任务（针对Receive Task）
     *
     * @param processInstId 流程实例
     * @param activityId    节点ID
     */
    @PostMapping("instance/task/trigger")
    public Rt<?> triggerTask(@NotBlank(message = "流程实例ID不能为空") @RequestParam String processInstId, @NotBlank(message = "节点ID不能为空") @RequestParam String activityId) {
        processManageService.triggerTask(processInstId, activityId);
        return Rt.success("触发任务成功！");
    }

    /**
     * 获取流程任务详情
     *
     * @param processInstId 流程实例
     * @param assignee      当前代理人
     */
    @PostMapping("instance/task/detail")
    public Rt<?> detailTask(@NotBlank(message = "流程实例ID不能为空") @RequestParam String processInstId, @NotBlank(message = "当前代理人不能为空") @RequestParam String assignee) {
        ProcTask task = processManageService.detailTask(processInstId, assignee);
        return Rt.success(task);
    }

    /**
     * 查询流程活动流转信息
     *
     * @param request
     * @return
     */
    @PostMapping("instance/task/his")
    public PageResponse<ProcHisTask> getProcTaskHis(@RequestBody PageRequest<ProcHisTask> request) {
        return processManageService.getProcTaskHis(request);
    }

    /**
     * 查看流程实例进度图片
     *
     * @param processInstId 流程实例ID
     * @throws Exception 异常
     */
    @GetMapping("instance/process/image")
    public ResponseEntity<byte[]> getProcessImage(@NotBlank(message = "流程实例ID不能为空") @RequestParam String processInstId) {
        String tenantId = Ft.Local.TenantId();
        try (InputStream inputStream = processManageService.getInstanceDiagram(processInstId, tenantId);
             ByteArrayOutputStream outputStream = new ByteArrayOutputStream();) {

            byte[] buffer = new byte[1024];
            int length;
            while ((length = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, length);
            }
            byte[] data = outputStream.toByteArray();
            return ResponseEntity.ok()
                    .contentType(MediaType.IMAGE_PNG)
                    .body(data);
        } catch (Exception e) {
            logger.error("流程信息图生成失败", e);
            throw new BizException(RtCodeEnum.R_ERROR.getCode(), "流程信息图生成失败！");
        }
    }

    /**
     * 查询所有的作业异常信息
     *
     * @param request
     * @return
     */
    @PostMapping("instance/exception/page")
    public PageResponse<ProcException> getAllException(@RequestBody PageRequest<ProcException> request) {
        String tenantId = Ft.Local.TenantId();
        request.getParam().setTenantId(tenantId);
        return processManageService.getAllException(request);
    }

    /**
     * 查询作业异常堆栈信息
     *
     * @param jobId 任务ID
     * @return
     */
    @PostMapping("instance/exception/trace")
    public Rt<?> getExceptionTrace(@NotBlank(message = "作业ID不能为空") @RequestParam String jobId) {
        String stacktrace = processManageService.getExceptionTrace(jobId);
        return Rt.success("成功！", stacktrace);
    }

    /**
     * 处理异常
     *
     * @param executionId 执行ID
     */
    @PostMapping("instance/exception/deal")
    public Rt<?> dealException(@NotBlank(message = "执行ID不能为空") @RequestParam String executionId) {
        processManageService.dealException(executionId);
        return Rt.success("处理异常成功！");
    }
}
